﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using Mbs.Collections;
using Mbs.ComponentModel.Container.Activation;
using Mbs.ComponentModel.Container.Context;
using Mbs.ComponentModel.Container.Injection;
using Mbs.ComponentModel.Container.Lifestyle;
using Mbs.ComponentModel.Container.Listner;
using Mbs.ComponentModel.Container.Loader;
using Mbs.ComponentModel.Container.Metadata;
using Mbs.Reflection;

namespace Mbs.ComponentModel.Container
{
   
    [Serializable]
    public class Kernel: MarshalAndDisposableObject,IKernel
    {
        protected readonly IMap<string, Pair> IdStores;
        protected readonly IMap<Type, List<Pair>> TypeStores;

        protected readonly IKernelListner Listner;

        [Serializable]
        protected class Pair
        {
            public IComponentInfo Component { get; internal set; }
            public ILifestyleManager LifestyleManager { get; internal set; }

            public Pair(IComponentInfo info, ILifestyleManager lifestyleManager)
            {
                Component = info;
                LifestyleManager = lifestyleManager;
            }
        }

        public Kernel():this(
            new KernelListnerManaer()
            , new LifestyleManagerFactory()
            , new ActivatorFactory()
            , new ClassLoader()
            , new LoaderManager()
            , new InjectStrategyManager())
        {
        }

        public Kernel(
            IKernelListnerManager listnerManager, 
            ILifestyleManagerFactory lifestyleManagerFactory,
            IActivatorFactory activatorFactory,
            IClassLoader classLoader, 
            ILoaderManager loaderManager, 
            IInjectStrategyManager injectStrategyManager)
        {
            ListnerManager = listnerManager;
            LifestyleManagerFactory = lifestyleManagerFactory;
            ActivatorFactory = activatorFactory;
            LoaderManager = loaderManager;
            InjectStrategyManager = injectStrategyManager;

            IdStores = new ConcurrentMap<string, Pair>(StringComparer.OrdinalIgnoreCase);
            TypeStores = new ConcurrentMap<Type, List<Pair>>();

            Listner = ListnerManager as IKernelListner;

            RegisterInstance("ServiceLocator", typeof(IServiceLocator), this);
            RegisterInstance("ServiceRegistry", typeof(IServiceRegistry), this);
            ServiceLocator.Current = this;
            ServiceRegistry.Current = this;

            if (loaderManager != null)
                loaderManager.Init(this);

            if (classLoader != null)
                RegisterInstance(AppDomain.CurrentDomain.Id.ToString() + ":" + classLoader.GetType(), typeof(IClassLoader), classLoader);

            if (injectStrategyManager != null)
                InjectStrategyManager = new InjectStrategyManager();

            RegisterInstance("inject manager", typeof(IInjectStrategyManager), InjectStrategyManager);
            

            if (ListnerManager != null)
                ListnerManager.Init(this);
        }


        #region IKernel Members
        public IActivatorFactory ActivatorFactory { get; private set; }
        public IKernelListnerManager ListnerManager { get; private set; }
        public ILoaderManager LoaderManager { get; private set; }
        public ILifestyleManagerFactory LifestyleManagerFactory { get; private set; }
        public IInjectStrategyManager InjectStrategyManager { get; private set; }

        public bool HasRegister(string id)
        {
            if (string.IsNullOrEmpty(id))
                throw new ArgumentNullException("id");

            return IdStores.ContainsKey(id);
        }

        public bool HasRegister(Type service)
        {
            if (service == null)
                throw new ArgumentNullException("service");

            if (TypeStores.ContainsKey(service))
                return true;
            if (!service.IsGenericType)
                return false;
            if (service.IsOpenGenericType())
                return false;
            return TypeStores.ContainsKey(service.GetGenericTypeDefinition());
        }

        public IComponentInfo GetComponent(string id)
        {
            if (HasRegister(id))
                return IdStores[id].Component;
            return null;
        }

        public IServiceRegistry Register(IComponentInfo info)
        {
            if (HasRegister(info.Id))
                throw new RepeatRegistrationException(
                       String.Format("There is a component already registered for the given key {0}", info.Id));

            var activator = CreateActivator(info);

            var lifestyleMgr = CreateLifestyleManager(info, activator);

            var pair = new Pair(info, lifestyleMgr);
            IdStores[info.Id] = pair;

            if (info.Service != null)
            {
                if (!TypeStores.ContainsKey(info.Service))
                    TypeStores[info.Service] = new List<Pair>();

                TypeStores[info.Service].Add(pair);
            }

            Listner.OnPreCreate(info);

            pair.LifestyleManager = CreateProxyLifestyleManager(info, pair.LifestyleManager);

            if (!info.IsLazyInit)
                Get(info.Service);

            return this;
        }

        private ILifestyleManager CreateLifestyleManager(IComponentInfo info, IActivator activator)
        {
            var lifestyleMgr = LifestyleManagerFactory.Create(info.Lifestyle);
            if (info.ExtendedProperties.Contains("proxy"))
                lifestyleMgr = new ProxyLifestyleManager(lifestyleMgr);

            lifestyleMgr.Init(activator, this, info, Listner.OnDestroyed);
            return lifestyleMgr;
        }

        private ILifestyleManager CreateProxyLifestyleManager(IComponentInfo info, ILifestyleManager lifestyleMgr)
        {
            if (info.ExtendedProperties.Contains("proxy"))
                lifestyleMgr = new ProxyLifestyleManager(lifestyleMgr);

            return lifestyleMgr;
        }

        private IActivator CreateActivator(IComponentInfo info)
        {
            var activator = ActivatorFactory.Create(info.Activator);
            activator.Init(Listner.OnCreated);

            var delegateActivator = activator as DelegateActivator;
            if (delegateActivator != null)
                delegateActivator.Creator = info.Factory;
            return activator;
        }

        protected virtual IComponentInfo CreateComponentInfo(string id, Type service, Type implementation, string activator, string lifestyleType, bool lazyInit)
        {
            var info = new ComponentInfo(id, service, implementation, activator,lifestyleType, lazyInit);
            return info;
        }

        protected virtual CreateContext NewCreateContext(IDictionary<string, object> args, IComponentInfo info,params Type[] genericParameters)
        {
            return new CreateContext(this, info, args,genericParameters);
        }

        protected virtual CreateContext NewCreateContext(IComponentInfo info, object[] args, params Type[] genericParameters)
        {
            return new CreateContext(this, info, args,genericParameters);
        }

        public virtual IServiceRegistry RegisterInstance(string id, Type service, object instance)
        {
            Trace.Assert(service != null, "service == null");
            Trace.Assert(instance != null, "instance == null");
            Trace.Assert(service.IsAssignableFrom(instance.GetType()), "!service.IsAssignableFrom(instance.GetType())");

            if (string.IsNullOrEmpty(id))
                id = instance.GetType().FullName;

            if (HasRegister(id))
                throw new RepeatRegistrationException(
                       String.Format("There is a component already registered for the given key {0}", id));

            var info = CreateComponentInfo(id, service, instance.GetType(), ActivatorType.Instance, LifestyleType.Singleton, false);
            info.ExtendedProperties.Set("instance", instance);

            var activator = ActivatorFactory.Create(info.Activator);
           
            var lifestyleMgr = CreateLifestyleManager(info,activator);
            lifestyleMgr.Init(activator, this, info, Listner.OnDestroyed);

            var pair = new Pair(info, lifestyleMgr);

            IdStores[id] = pair;
            if (!TypeStores.ContainsKey(service))
                TypeStores[service] = new List<Pair>();

            TypeStores[service].Add(pair);

            Listner.OnPreCreate(info);

            pair.LifestyleManager = CreateProxyLifestyleManager(info, pair.LifestyleManager);

            Listner.OnCreated(info, instance);

            return this;
        }

        public IServiceRegistry RegisterInstance(string id, object instance)
        {
            Trace.Assert(instance != null, "instance == null");
            return RegisterInstance(id, instance.GetType(), instance);
        }

        public void UnRegister(Type service)
        {
            if (HasRegister(service))
            {
                var pair = TypeStores[service][0];
                if (pair.LifestyleManager.Release())
                {
                    pair.Component.ExtendedProperties.Clear();
                    TypeStores.Remove(service);
                }

            }

            
        }

        public void UnRegister(string id)
        {
            if (HasRegister(id))
            {
                var pair = IdStores[id];
                if (pair.LifestyleManager.Release())
                {
                    pair.Component.ExtendedProperties.Clear();
                    IdStores.Remove(id);
                }
            }
        }


        public void UnRegister(object instance)
        {
            //throw new NotImplementedException();
        }
        #endregion

       
        #region IServiceLocator Members

        public object Get(string id, IDictionary<string,object> args)
        {
            return GetByNamedArgs(id, args);
        }

        private object GetByNamedArgs(string id, IDictionary<string, object> args)
        {
            if (!HasRegister(id))
                return null;

            var pair = IdStores[id];
            var rs = pair.LifestyleManager.Get(NewCreateContext(args, pair.Component));
            return rs;
        }

        public object Get(Type service, IDictionary<string, object> args)
        {
            return GetByNamedArgs(service, args);
        }

        private object GetByNamedArgs(Type service, IDictionary<string, object> args)
        {
            if (!HasRegister(service))
                return null;

            Pair pair = null;
            if (service.IsCloseGenericType() && HasRegister(service.GetGenericTypeDefinition()))
                pair = TypeStores[service.GetGenericTypeDefinition()][0];
            else
                pair = TypeStores[service][0];

            return pair.LifestyleManager.Get(NewCreateContext(args, pair.Component, GetGenericParameters(service)));
        }

        private static Type[] GetGenericParameters(Type service)
        {
            if (service.IsGenericType)
                return service.GetGenericArguments();
            return null;
        }

        public object Get(string id, params object[] args)
        {
            return GetByOptionArgs(id, args);
        }

        private object GetByOptionArgs(string id, object[] args)
        {
            if (!HasRegister(id))
                return null;

            var pair = IdStores[id];
            var rs = pair.LifestyleManager.Get(NewCreateContext(pair.Component, args));
            return rs;
        }

        public object Get(Type service, params object[] args)
        {
            return GetByOptionArgs(service, args);
        }

        private object GetByOptionArgs(Type service, object[] args)
        {
            if (!HasRegister(service))
                return null;

            Pair pair = null;
            if (service.IsCloseGenericType() && HasRegister(service.GetGenericTypeDefinition()))
                pair = TypeStores[service.GetGenericTypeDefinition()][0];
            else
                pair = TypeStores[service][0];
            return pair.LifestyleManager.Get(NewCreateContext(pair.Component, args, GetGenericParameters(service)));
        }

        public object Get(string id)
        {
            return GetByOptionArgs(id, null);
        }

        public object Get(Type service)
        {
            return GetByOptionArgs(service, null);
        }

        public object[] GetAll(Type service)
        {
            if (!HasRegister(service))
                return new object[0];

            var ctx = new CreateContext(this, GetGenericParameters(service));

            int count = TypeStores[service].Count;
            object[] result = new object[count];

            for (var i = 0; i < count; i++)
                result[i] = TypeStores[service][i].LifestyleManager.Get(ctx.Init(TypeStores[service][i].Component));

            return result;
        }

        public T[] GetAll<T>()
        {
            var service = typeof(T);
            if (!HasRegister(service))
                return new T[0];

            var ctx = new CreateContext(this, GetGenericParameters(service));

            int count = TypeStores[service].Count;
            T[] result = new T[count];

            for (var i = 0; i < count; i++)
                result[i] = (T)TypeStores[service][i].LifestyleManager.Get(ctx.Init(TypeStores[service][i].Component));

            return result;
        }
        #endregion

        #region IServiceProvider Members

        public object GetService(Type serviceType)
        {
            return Get(serviceType);
        }

        #endregion

        #region IDisposable Members
        protected override void Dispose(bool disposing)
        {
            //TODO:

            ListnerManager.Dispose();
        }

        #endregion

    }
}
